﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NDde.Client;

namespace TosObserver
{
    public class TosFeed : IObservable<TosFeed>, IDisposable // inspired from http://www.remondo.net/observer-pattern-example-csharp-iobservable/
    {
        public string Symbol { get; private set; }
        public TOSTopic Topic { get; private set; }
        public DdeClient client;

        public TosFeed(string symbol, TOSTopic topic)
        {
            Symbol = symbol;
            Topic = topic;

            client = new DdeClient("TOS", Topic.Name);

            // Subscribe to the Disconnected event.  This event will notify the application when a conversation has been terminated.
            // do we need it?     client.Disconnected += OnDisconnected;

            // Connect to the server.  It must be running or an exception will be thrown.
            client.Connect();

            // Advise Loop
            client.StartAdvise(Symbol, 1, true, 60000);
            client.Advise += OnAdvise;
        }

        private void OnAdvise(object sender, DdeAdviseEventArgs args)
        {
            decimal d;
            string input = args.Text;

            if (Topic.Name == "PERCENT_CHANGE")
                input = input.Replace("%", string.Empty);
            if (Topic.Name == "MARKET_CAP")
                input = input.Replace("M", string.Empty);

            if (decimal.TryParse(input, out d))
                Value = d;
        }

        private void OnDisconnected(object sender, DdeDisconnectedEventArgs args)
        {
            //Console.WriteLine(
            //"OnDisconnected: " +
            //"IsServerInitiated=" + args.IsServerInitiated.ToString() + " " +
            //"IsDisposed=" + args.IsDisposed.ToString());
        }

        private List<IObserver<TosFeed>> observers = new List<IObserver<TosFeed>>();

        private decimal value_;

        public decimal Value
        {
            get { return value_; }
            set
            {
                value_ = value;
                this.Notify(this);
            }
        }

        public IDisposable Subscribe(IObserver<TosFeed> observer)
        {
            lock (this.observers)
            {
                if (!this.observers.Contains(observer))
                {
                    observers.Add(observer);
                }

                return new Unsubscriber(this.observers, observer);
            }
        }

        private void Notify(TosFeed newsFeed)
        {
            lock (this.observers)
            {
                foreach (var observer in this.observers)
                {
                    if (this.value_ == decimal.MinValue) // just to show the error behavior pattern
                    {
                        observer.OnError(new Exception("Something wrong"));
                    }
                    else
                    {
                        observer.OnNext(this);
                    }
                }
            }
        }

        private void StopReading()
        {
            lock (this.observers)
            {
                foreach (var observer in this.observers)
                {
                    if (this.observers.Contains(observer))
                    {
                        observer.OnCompleted();
                    }

                    observers.Clear(); // No more dangling events!
                }
            }
        }

        class Unsubscriber : IDisposable
        {
            private List<IObserver<TosFeed>> _observers;
            private IObserver<TosFeed> _observer;

            public Unsubscriber(List<IObserver<TosFeed>> observers,
                IObserver<TosFeed> observer)
            {
                this._observers = observers;
                this._observer = observer;
            }

            public void Dispose()
            {
                if (this._observer != null && this._observers.Contains(this._observer))
                {
                    this._observers.Remove(_observer);
                }
            }
        }

        public void Dispose()
        {
            client.Dispose();
        }
    }
}
